﻿using System;
using System.Collections.Generic;
using BrainTechLLC.ImageOperatorsNonAForgeDependent;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Drawing;
using System.IO;

namespace BrainTechLLC.WebCamLib3
{
    /// <summary>
    /// Interaction logic for CameraControl.xaml
    /// </summary>
    public partial class CameraControl : UserControl
    {
        public event EventHandler<ObjectEventArgs<CameraHandler>> CameraHandlerCreated;

        public FrameInfo _latestFrameInfo;
        public FrameInfo _currentlyRenderingFrame;
        public FrameInfo _frameStillBeingDisplayed;

        public List<IPostProcessor> _postProcessors;
        public CameraHandler cameraHandler;
        public LocalVideoDevice _videoDeviceToUse;        

        public CameraControl()
        {
            InitializeComponent();
        }

        public List<IPostProcessor> PostProcessors
        {
            get { return _postProcessors; }
        }
        
        protected override void OnRender(DrawingContext drawingContext)
        {
            PerformRender(drawingContext);
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
            // InvalidateVisual();
        }

        public void ResetFPS()
        {
            if (cameraHandler != null)
                cameraHandler.ResetFPS();
        }

        public double CalcFPS()
        {
            if (cameraHandler == null)
                return 0;

            return cameraHandler.CalcFPS();
        }

        private void PerformRender(DrawingContext g)
        {
            FrameInfo frameToDraw = null;

            if (_latestFrameInfo == null)
                return;

            Interlocked.Exchange<FrameInfo>(ref frameToDraw, _latestFrameInfo);

            if (frameToDraw == null)
            {
                // Yanked out from right under our nose... sneaky
                return;
            }

            if (object.ReferenceEquals(frameToDraw, _frameStillBeingDisplayed))
            {
                return;
            }

            _currentlyRenderingFrame = frameToDraw;

            //try
            //{
            Bitmap bmToDraw = frameToDraw.CreateBitmap();

            //if (frameToDraw._bitmap == null)
            //{
            //    bmToDraw = frameToDraw.Bitmap;

            //    if (bmToDraw == null)
            //    {
            //        _currentlyRenderingFrame = null;
            //        Interlocked.CompareExchange<FrameInfo>(ref _latestFrameInfo, null, frameToDraw);
            //        return;
            //    }
            //}
            //else
            //{
            //    // someone has already rendered the bitmap and/or modified it with pre/post filters
            //    // For safety's sake, we'll make a copy...
            //    try
            //    {
            //        bmToDraw = new Bitmap(frameToDraw._bitmap);
            //    }
            //    catch
            //    {
            //        bmToDraw = frameToDraw.CreateBitmap();
            //    }
            //    // bmToDraw = frameToDraw._bitmap; // probably not
            //}

            var imageSource = bmToDraw.CreateImageSourceFromBitmap(null);

            // Scaling.GetScaledImageDimensions((double)frameToDraw.Width , (double)frameToDraw.Height,graphContext.RenderSize.Width, graphContext.RenderSize.Height);

            g.DrawImage(imageSource, new Rect(RenderSize));

            var oldFrame = _frameStillBeingDisplayed;            
            _currentlyRenderingFrame = null;
            _frameStillBeingDisplayed = frameToDraw;

            if (oldFrame != null)
            {
                oldFrame.ClearBitmapAndBytes();
            }
            
            if (!object.ReferenceEquals(_latestFrameInfo, frameToDraw))
            {
                // a new frame has been posted already....
                Dispatcher.BeginInvoke(new VoidDelegate(() =>
                    {
                        if (_currentlyRenderingFrame == null && !object.ReferenceEquals(_latestFrameInfo, _frameStillBeingDisplayed))
                        {
                            this.InvalidateVisual();
                        }
                    }),
                    System.Windows.Threading.DispatcherPriority.Normal);
            }
            else
            {
                // we're still on the same frame... no new one has been posted yet                    
                Interlocked.CompareExchange<FrameInfo>(ref _latestFrameInfo, null, frameToDraw);
            }
            //}
            //catch (Exception ex)
            //{
            //    Console.WriteLine(ex.ToString());
            //}
        }

        public void ShutdownCameraControl()
        {
            if (cameraHandler != null)
            {
                cameraHandler.NewFrameReceived -= webCamControl_NewFrameReceived;
                cameraHandler.StopProcessingImages();
                Thread.Sleep(50);
                cameraHandler.StopCameraCapture();
                Thread.Sleep(30);
            }
        }

        public void SetPostProcessors(List<IPostProcessor> postProcessors)
        {
            _postProcessors = new List<IPostProcessor>(postProcessors);
        }

        public void SetVideoDevice(LocalVideoDevice device)
        {
            _videoDeviceToUse = device;
        }

        public void StartupWebCam()
        {
            if (_videoDeviceToUse == null)
                throw new NullReferenceException("You must set the video device you want to use before firing it up... Use SetVideoDevice");

            if (cameraHandler == null)
            {
                cameraHandler = new CameraHandler();

                if (_postProcessors != null)
                {
                    for (int n = 0; n < _postProcessors.Count; n++)
                    {
                        var postProcessor = _postProcessors[n];
                        cameraHandler.PostProcessors.Add(postProcessor.Name, postProcessor);
                    }
                }

                if (CameraHandlerCreated != null)
                {
                    CameraHandlerCreated(this, new ObjectEventArgs<CameraHandler>(cameraHandler));
                }
            }

            // try opening the device
            Exception ex;

            if (!cameraHandler.OpenCaptureDevice(_videoDeviceToUse, out ex))
            {
                throw new Exception("Unable to open the capture/video device", ex);
            }

            while (!cameraHandler.VideoCaptureProcessing && cameraHandler.VideoCaptureError == null)
            {
                // wait for it...
                Thread.Sleep(200);
            }

            BeginProcessing();            
        }

        public void BeginProcessing()
        {
            // We're ready... start eating up the incoming images...
            Dispatcher.BeginInvoke(new VoidDelegate(() =>
            {
                string error;

                if (!cameraHandler.BeginProcessingImages(out error))
                {
                    throw new Exception(string.Format("Error when attempting to start image processing: {0}", error));
                }
            }));

            cameraHandler.NewFrameReceived += new EventHandler<ObjectEventArgs<FrameInfo>>(webCamControl_NewFrameReceived);
        }

        private void webCamControl_NewFrameReceived(object sender, ObjectEventArgs<FrameInfo> e)
        {
            if (_latestFrameInfo == null || 
                (_currentlyRenderingFrame != null &&  !object.ReferenceEquals(_latestFrameInfo, _frameStillBeingDisplayed))) // The second part may slow us down
            {
                Interlocked.Exchange<FrameInfo>(ref _latestFrameInfo, e.obj);
            }
            
            Dispatcher.BeginInvoke(new VoidDelegate(() => this.InvalidateVisual()), System.Windows.Threading.DispatcherPriority.Send);
        }

    }
}
